<?php
// +----------------------------------------------------------------------
// | 杭州十禾信息科技有限公司
// +----------------------------------------------------------------------
// | Copyright (c) 2013-2015 http://www.shelpe.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: LeePuman <ysyzlsm@163.com>
// +----------------------------------------------------------------------

class AutoDriveBaseModel extends NewzanModel{
	protected $tableName = 'auto_pro_product_queue';
	// =============== Used Models ===============
	/** @var RptKeywordModel */
    private $_rptKeywordModel = null;
    /** @var ProductModel */
    private $_productMdl = null;
    /** @var KeywordModel */
    private $_keywordMdl = null;
    /** @var WordMdl */
    private $_wordMdl = null;
    /** @var ApiModel */
    private $_apiMdl = null;
    /** @var ProductQueueModel */
    private $_autoProPqMdl = null;
    /** @var AdgroupModel */
    private $_adgroupMdl = null;
    /** @var CategoryModel */
    private $_cateMdl = null;
    /** @var Carine */
    private $_carine = null;

    // =============== Properties ===============
    // 以下这6个属性尽量在init()中设定。
    /** @var array(key=>val) 一条adgroups表记录 */
    protected $_adgroup = null;
    /** @var array(key=>val) 一条auto_pro_product_queue表记录 */
    protected $_pqProduct = null;
    /** @var int adgroup所属用户ID */
    protected $_userId = 0; 
    /** @var int 商品所属最低级目录ID */
    protected $_cateId = 0;
    /** @var string 出价类型: @see AutoDriveBaseModel::$PRICE_TYPES 的值 */
    protected $_priceType = '';
    /** @var string 出价类型的编号: @see AutoDriveBaseModel::$PRICE_TYPES 的主键. */
    protected $_priceTypeId = 1;
    // 属性$_cndWords主要是自动添加新词的时候跟子类作为共享变量。在addProKeywords()中设定。
    /** @var array(string=>int) 二维数组array(word => weight) */
    protected $_cndWords = null;
    /** @var array(k=>v) 
     * 自动开车所需要用的data. 可以在_prepareOtherData()中赋值，也可以在_checkData()中调用, 也可以使用_checkDataXX()直接调用.
     * 所有可设参数都需要有对应的_checkDataXX()方法.
     * 具体XX为: 	CateSim (类目相关性), ProdSim (产品相关性), Similarity (加权相关性), PQR (性价比),
     * 			CateStat3Days (类目平均3天数据), WordCateStats3Days (词在类目下3天数据), 
     * 			WordTbStats3Days (词在全网3天数据)
     * 获取数据可以使用 getDriveData(XX)
     */
    protected $_driveData = null;
    /** @var array(k=>v) 自动开车可调参数. 在setDriveParams()中设置. 
     * 各个参数默认值见init()方法后半部分.
     * 可设参数为:
     * <ul>
     * <li>sortSimWeight: float [0, 1], 综合相似度在排序时所占权重. </li>
     * <li>cutThres: float [0, 1], 选词时<strong>排序权重</strong>低于此分数的词将不被选取. </li>
     * <li>priceSimWeight:  float [0, 1], 综合相似度在出价时所占权重. </li>
     * <li>maxPriceRatio: float [0, 3], 出价时最多(综合得分为最高分1时)取给定词的平均出价的多少倍. </li>
     * <li>minPriceRatio: float [0, 1], 出价时最小(综合得分为最低分0时)取给定词的平均出价的多少倍. 
     * 			这个参数主要是用来结合minPriceRatio定得分出价图的斜率, 因为综合得分为0的词不会被取上.</li> 
     * </ul>
     */
    protected $_driveParams = null;

    // =============== Constants =============== 
    /** @var int 淘宝允许的关键词的最低出价 */
    /** @var 自动开车关键词最大出价(单位: 分). 只在自动关键词出价时使用. */
    const MIN_PRICE = 6;
    const DEFAULT_WORD_MAX_PRICE = 999;
    /** @var 自动开车定向投放和类目出价 最大出价: 59(单位: 分).
     * 目前初始adgroup, 类目, 定向投放 的出价全部为这个.  */
    const DEFAULT_NONSEARCH_MAX_PRICE = 59;
    /** @var int 默认关键词投放匹配方式: 4 */
    const DEFAULT_MATCHSCOPE = 4;
    /** @VAR int 每个adgroup能投放的关键词的最大数目 */
    const MAX_WORDS_PER_ADGROUP = 200;
    /** @var int 默认自动开车选词方式: 智能平衡模式 */
    const DEFAULT_PRICE_TYPE = 1;
    /** @var array(int => string) 自动开车选词方式.  */
    public static $PRICE_TYPES = array(
    		1 => 'ctr_price_desc', // 智能平衡模式
    		2 => 'pv_desc', // 引流模式
    		3 => 'price_asc', // 小额投资模式
    		4 => 'super_save', // 超省油广撒网
    );
    public static $PRICE_TYPES_RESV = array(
    		'ctr_price_desc' => 1, // 智能平衡模式
    		'pv_desc' => 2, // 引流模式
    		'price_asc' => 3, // 省油（小额投资）模式
    		'super_save' => 4, // 超省油广撒网
    );
    /** 不同模式的维护周期（即每隔多少天更新一次） */
	public static $MAINTAIN_PERIOD = array(
			1 => 2,
			2 => 2,
			3 => 2,
			4 => 1,
	);
	
    protected static $ZERO_STAT = array(
    		'impressions'=>0,
    		'ctr'=>0,
    		'click'=>0,
    		'pay_count'=>0,
    		'cpc'=>0,
    		'sales'=>0,
    		'avgpos'=>0,
    		'cost'=>0,
    );
    
    
    /**
     * 初始化实例, 实际上相当于构造函数. 因为父类的父类Model已经定义了一个比较屎的构造函数, 
     * 这里不好重构,只能用init()代替.
     * <strong>注意: </strong> 这个方法不会自动调用, 需要手动调用!
     * 
     * @param DbRow|int $adgroup 一条adgroup记录或者ID
     * @param int $userId 用户ID
     * @param DbRow $pqProduct 一条auto_pro_product_queue记录
     * @param string $priceType self::$PRICE_TYPES中3个值之一。
     * @return AutoDriveBaseModel 初始化过后的自身
     * @throws NewzanException 当参数异常时, 抛出此错误.
     */
    public function init($adgroup, $userId = 0, $pqProduct = null, $priceType = null) {
    	if(!is_array($adgroup) && $userId == 0)
    		//throw_exception('$adgroup为ID时，必须给定$userId。', 'NewzanException', RetStatus::PARAM_ERR);
    		throw new NewzanException('$adgroup为ID时，必须给定$userId。');
    	$this->_adgroup = is_array($adgroup)? $adgroup : $this->_getAdgroupMdl()->getAdgroupByAdgroupId($userId , $adgroup, false);
    	if(!is_array($this->_adgroup))
    		//throw_exception('给定推广组不存在。', 'NewzanException', RetStatus::PARAM_ERR);
    		throw new NewzanException('给定推广组不存在。');
    	$this->_pqProduct = $pqProduct ? $pqProduct : $this->_getAutoProPqMdl()->getItem($this->_adgroup['num_iid'], $this->_adgroup['campaign_id']);
    	$this->_userId = $userId != 0 ? $userId : $this->_adgroup['user_id'];
    	$this->_cateId = CategoryModel::getAdgroupCate($this->_adgroup);
    	$this->_priceTypeId = $priceType ? self::$PRICE_TYPES_RESV[$priceType] 
    			: ($this->_pqProduct &&$this->_pqProduct['type']>0? $this->_pqProduct['type'] : AutoDriveBaseModel::DEFAULT_PRICE_TYPE);
    	$this->_priceType = AutoDriveBaseModel::$PRICE_TYPES[$this->_priceTypeId];
    	
    	$this->_initStartParams();
    	$this->_driveData = NULL;
    	 
    	return $this;
    }
    
    /**
     * 初始化自动开车初始开车的默认参数。
     */
    protected function _initStartParams() {
    	// 设置自动开车初始化出价的默认参数. 非默认参数可以通过 setDriveParams()设置.
    	// 每个变量的默认值依次为 (智能, 引流, 省油)
    	$defaultDriveParams = array(
    			'sortSimWeight' => array(0.8, 0.7, 0.9),
    			'cutThres' => array(0.27, 0.29, 0.3),
    			'priceSimWeight' => array(0.7, 0.8, 0.85), // 卫平-2013.06.26: 现在改为省油计划给精确词更高价格。以前为：array(0.65, 0.75, 0.55),
    			'maxPriceRatio' => array(2.03, 2.13, 1.71),
    			'minPriceRatio' => array(0.31, 0.51, 0.23),
    			'pqrRatioRatio' => array(1, 1.3, 0.8, 0),
    			'statDays' => 3, // 关键词短期表现所考虑的天数
    			'lowProdSimThres' => 0.05,
    			'lowCateSimThres' => 0.15,
    	);
    	$priceTypeId = $this->_priceTypeId - 1;
    	foreach($defaultDriveParams as $name => $vals) {
    		$this->_driveParams[$name] = is_array($vals) ? $vals[$priceTypeId] : $vals;
    	}
    	return $this;
    }
    
    /**
     * 供子类专用, 初始化子类所特需数据. 
     * 将在获取词并且过滤掉无用词之后调用.
     * 
     * @return AutoDriveBaseModel 返回自身
     */
    protected function _prepareOtherData() { 
    	return $this;
    }
    
    /**
     * @deprecated
     * 自动设置adgroup的类目出价和关键词默认出价。
     */
    public function setCatmatchPrice() {
    	throw new NewzanException("类目出价已经取消");
    	if(!$this->_adgroup)
    		throw_exception('还未调用init()初始化adgroup。');
    	 
    	$userId = $this->_userId;
    	$adgroupId = $this->_adgroup['adgroup_id'];
    	$adgroupMdl = $this->_getAdgroupMdl();

    	//设定类目出价
    	$max_price = 0;
    	$catmatch = $adgroupMdl->getAdgroupCatMatch($userId, $adgroupId);
    	if( !$catmatch ){
    		throw new NewzanException('自动开车设置类目出价失败');
    	} else {
    		//获取类目一个月内平均出价水平
    		if( $this->_cateId ){
    			//得到叶子类目来获取类目出价平均值
    			$this->_checkDataIndustryStats();
    			$industryAvgCpc = $this->_driveData['IndustryStats']['trend_ppc_seller'];
    			
    			// getCategoryAvgBase($this->_cateId);
    			$defaultCatmatchRatios = array(1.3, 1.6, 1.0, 1);
    			//更新类目出价
    			if($industryAvgCpc > 0){
    				$max_price = round($industryAvgCpc * $defaultCatmatchRatios[$this->_priceTypeId - 1]);
    				$params = array(
    						'adgroup_id' => $adgroupId,
    						'catmatch_id' => $catmatch['catmatch_id'],
    						'max_price' => $max_price,
    						'use_default_price' => 'false',
    						'online_status' => 'online'
    				);
    				
    				$adgroupMdl->updateAdgroupCatMatch($userId, $params, true);
    			} // TODO 如果没有行业数据怎么办呢？
    		}
    	}
    	
    	//更新推广组default出价为类目出价的0.75 倍
    	if($max_price) {
    		$adgroupMdl->updateAdgroup($userId, $adgroupId, 
    				array('default_price' => round($max_price * 0.75)), true);
    	}
    	
    	return true;
    }
    


    /**
     *  添加自动投放任务的产品为广告组,获得广告组id(adgroup_id).
     *
     *  @param array $pqProduct	auto_pro_product_queue表的一条记录
     *  @return false|int	如果创建失败返回false, 否则返回推广组ID.
     */
    public function createAutoAdgroup($pqProduct) {
    	$adgroupId = 0;
    	$this->startTrans();
    	try {
	    	$adgroup = array(
	    			'item_id' => $pqProduct["product_id"],
	    			'title' => zh_substr($pqProduct["title1"].$pqProduct["title2"], 40),
				//'title' => $pqProduct["title1"],
	    			'img_url' => $pqProduct["img_url1"],
	    			'campaign_id' => $pqProduct['campaign_id'],
	    			'default_price' => $pqProduct['default_price'] ? $pqProduct['default_price']
	    					: AutodriveBaseModel::DEFAULT_NONSEARCH_MAX_PRICE,
	    			'status' => $pqProduct['need_confirm'] ? AdgroupModel::STATUS_WAITING_CONFIRM : AdgroupModel::STATUS_IDLE,
	    	);
	    	$adgroupId = $this->_getAdgroupMdl()->addAdgroup($pqProduct['user_id'], $adgroup, true);
	    
	    	// add a adgroup if no id returned
	    	if($adgroupId){
	    		$this->_getAutoProPqMdl()->where( array('product_id'=>$pqProduct['product_id'], 'campaign_id'=>$pqProduct['campaign_id']))
	    				->save( array('adgroup_id' => $adgroupId) );
	    		 
	    		// create another creative
	    		$creativeMdl = new CreativeModel();
	    		$creative = array(
					'title' => zh_substr($pqProduct["title2"], 40),
	//				'title' => $pqProduct["title2"],
	    				'img_url' => $pqProduct["img_url2"],
	    				'adgroupId' => $adgroupId
	    		);
	    		$creativeMdl->addCreative($pqProduct['user_id'], $adgroupId, $creative, true);
	    		
	    		$this->commit();
	    		return $adgroupId;
	    	} else {
	    		$this->rollback();
	    		return false;
	    	}
    	} catch (Exception $e) {
    		$this->rollback();
    		
    		if($adgroupId)
    			$this->_getAdgroupMdl()->removeAdroupByApi($adgroupId);
    		
    		if($e instanceof TopClientException)
    			throw $e;
    		
    		Log::record($e->getMessage() . '\n' . $e->getTraceAsString(), Log::ERR);
    		return false;
    	}
    }
    

    /**
     * 自动维护一个广告组的所有关键词
     *
     * @param int[=2] $days 	维护所需要查看的数据天数
     */
    public function maintainAdgroupKeywords($days = 2) {
		if(!$this->_adgroup)
			throw_exception('还未调用init()初始化adgroup。');

		$userId = $this->_userId;
		$adgroupId = $this->_adgroup['adgroup_id'];
		$from = date("Y-m-d", strtotime((-$days-1) . ' day'));
    	$to = date("Y-m-d", strtotime('-1 day'));

	$this->logInfo('DEBUGGING...');
    	 
    	//首先更新一遍当前关键词的qscore
    	$syncMdl = new SyncModel();
    	$syncMdl->syncKeywords($userId, $adgroupId);
    
    	//再更新一下adgroup的报表
    	if(C('REALTIME_UPDATE_ADGROUP_KEYWORD_RPT') ){
    		//实时同步adgroup关键词报表
    		$this->_getRptKeywordMdl()->syncAdgroupKeywordReport($userId, $this->_adgroup['campaign_id'], $adgroupId);
    	}
    	 
    	// 获取数据
    	//$keywords = $this->getAdgroupKeywords($userId, $adgroupId, true);
    	//$keywords = $this->getKeywordsWithStats('adgroup_id', $adgroupId, $from, $to, '', '');
    	$keywords = $this->_getKeywordMdl()->where(array('adgroup_id'=>$adgroupId))->select(); //获取adgroup所有关键词
    	$keywordIds = array_column($keywords, 'keyword_id'); // array(number)
    	$wordStrs = array_column($keywords, 'word'); // array(string)
    	$stat3Day = $this->_getRptKeywordMdl()->getSummaryStats($userId, 'keyword_id', $keywordIds, 'impressions,ctr,click,sales,avgpos,cost',
    			$from, $to, false, '', 'average');
    	if(!$stat3Day) // 如果最近3天都没有数据，说明之前暂停了，刚刚重新开启。没必要更新。
    		return false;
    	$stat7Day = $this->_getRptKeywordMdl()->getSummaryStats($userId, 'keyword_id', $keywordIds, 'impressions,ctr,click',
    			date("Y-m-d", strtotime('-7 day')), $to, false, '', 'average');
    	$tbStats3day = $this->_getKeywordMdl()->getTbStats($wordStrs, 'avg_price,pv,ctr,competition', $from, $to, true);
    	
    	$zeroStat = array('impressions' => 0, 'ctr' => 0, 'click' => 0, 'sales' => 0, 'avgpos' => 0, 'cost' => 0);
    	 
    	/* 这个有必要吗？前面是否已经判断了？
    	 $adgroupMdl=new AdgroupModel();
    	$adgroup=$adgroupMdl->getAdgroupByAdgroupId($userId, $adgroup, false);
    	*/
    	 
    	// TODO 将$cateStat取消掉
    	$cateStat = null; //$this->_getCategoryMdl()->getCatBaseAvg($this->_cateId);
    	
    	// 逐个维护关键词
    	$wordsToDelete = array();
    	$wordsToUpdate = array();
    	//     	$existentWords = array();
    	foreach($keywords as $kid => $keyword){
    		//     		$existentWords[] = $keyword['word'];
    		$keywordId = $keyword['keyword_id'];
    
    		$this->maintainKeyword($keyword,
    				(isset($stat3Day[$keywordId]) ? $stat3Day[$keywordId] : $zeroStat),
    				(isset($stat7Day[$keywordId]) ? $stat7Day[$keywordId] : $zeroStat),
    				$tbStats3day[$keyword['word']], $cateStat,
    				$wordsToUpdate, $wordsToDelete);
    	}
    	$this->logDebug(count($wordsToDelete) . ' keywords to delete: ');
    	$this->logDebug(join(',', $wordsToDelete));
    	$this->logDebug(count($wordsToUpdate) . ' keywords to update: ');
    	$this->logDebug(join(',', $wordsToUpdate));
    	
    	// 提交需要删除的关键词
    	$apiMdl = $this->_getApiMdl();
    	if($wordsToDelete)
    		$this->_getKeywordMdl()->deleteKeywords($wordsToDelete, $this->_adgroup['campaign_id'], $apiMdl, true);
    	// 提交需要改价的关键词
    	if($wordsToUpdate)
    		$this->_getKeywordMdl()->updateKeywords($adgroupId, $wordsToUpdate, $apiMdl, true);
    
    	// 添加新词
    	$this->logDebug('开始添加新词');
    	$this->_cndWords = null; // 清空之前关键词维护时设定的 _cndWords.
    	$this->_driveData = null; // 清空之前关键词维护时的数据.
    	$this->_initStartParams()	
    			->setDriveParams( array('cutThres' => $this->_driveParams['cutThres']-0.05) )
    			->addProKeywords($wordStrs, false, 0);
    	
    	return true;
    }
    
    /**
     * 维护一个关键词. 关键指标 pv, ctr, cvr, qscore, avgpos
     *
     * @param array $keyword [引用传参] 一条keywords表记录
     * @param array(k=>v) $stat3Day 该关键词最近3天的表现（平均值）.
     * @param array(k=>v) $stat7Day 该关键词最近7天的表现（平均值）.
     * @param array(k=>v) $tbStat3Day 该关键词最近3天在淘宝全网的统计信息（平均值）. TODO 或者使用关键词在该类目下的统计信息更准确.
     * @param array(k=>v) $cateStat 该类目的统计信息（平均值）
     * 		包含 avg_price,click,pv,competition,ctr
     *
     * @param array2D $wordsToUpdate [引用传参] ( 'word' => '关键词' 'keyword_id' => 关键词ID, 'max_price' => 50 // 分为单位 'matchscope' => 1)
     * @param array(number) $wordsToDelete [引用传参] 要删除的关键词ID列表.
     * 	包含 cpc, ctr, impressions, cpm, click, cost, source, search_type, avgpos, keywordstr
     */
    protected function maintainKeyword(&$keyword, &$wordsToUpdate, &$wordsToDelete,
    		$stat3Day, $stat7Day, $tbStat3Day, $cateStat
    		) {
    	//     	$adgroupId = $adgroup['adgroup_id'];
    	$keywordId = $keyword['keyword_id'];
    	$oriPrice = $keyword['max_price'];
    	 
    	// 记录本次操作至auto_maintain_log
    	$lowAvgPosThres = 45;		// > 30为排名靠后
    	$topAvgPosThres = 16; 		// <=16为排名靠前
    	$lowQscoreThres = 3;		// <=2 为低
    	$middleQscoreThres  = 6;	// [3, 6]为中
    	$highQscoreThres = 8;		// >8为高
    	$avePv = 5 * $tbStat3Day['pv'] / $tbStat3Day['competition']; // 平均pv
    	$highCtrPercent = 1;
    	$autoLog = array(
    			'keyword_id' => $keywordId,
    			'date' => date('Y-m-d'),
    			'pv' => $stat3Day['impressions'] == 0 ? 0 : ($stat3Day['impressions'] <= $avePv ? 1 : 2),
    			'ctr' => $stat3Day['ctr'] == 0 ? 0 : ($stat3Day['ctr'] < ($highCtrPercent * $tbStat3Day['ctr'] ) ? 1 : 2),
    			'cvr' => $stat3Day['sales'] == 0 ? 0 : 1,
    			'qscore' => $keyword['qscore'] <= $lowQscoreThres ? 0 : ($keyword['qscore'] <= $middleQscoreThres ? 1 : ($keyword['qscore'] <= $highQscoreThres ? 2 : 3)),
    			'avgpos' =>  $stat3Day['avgpos'] > $lowAvgPosThres ? 0 : ($stat3Day['avgpos'] < $topAvgPosThres ? 1 : 2),
    	);
    	
    		//计算词的信价比
    //	asort($wordpqr);
    	//设大于pqrRatioRatios的
    	$thresthold = $this->_driveParams['pqrRatioRatio'] * $cateStat['avg_price'];
    	
//     	foreach($stat3Day as $word=>$states) {
    		$stat3DayClick = $stat3Day['impressions'] * $stat3Day['ctr'];
    		if($stat3DayClick > 0) {
    			if($stat3Day['cost'] / $stat3DayClick <= $thresthold){ // TODO 确保cost是能获取到的
    				$tbStat3Day['pqr'] = 1; //高信价比
    			} else {
    				$tbStat3Day['pqr'] = -1; //低信价比
    			}
    		} else {
    		  if($stat3Day['impressions'] == 0 && $keywords['max_price'] <= $thresthold) {
    		  	$tbStat3Day['pqr'] = 1; //高信价比
    		  } else {
    			$tbStat3Day['pqr'] = 0;
    		  }
    		}
//     	}
    	 
    	// 将 1) 低质量得分, 2) 无展现 且质量得分不高的词(<8) 的词先删除
    	$createTime = strtotime($keyword['create_time']);
    	
    	if($this->_priceTypeId == 3) {
    		$this->_maintainKeywordSave($keyword,
    				$stat3Day, $stat7Day, $tbStat3Day, $cateStat,
    				$wordsToUpdate, $wordsToDelete);
    	} else {
    		$this->_maintainKeywordNormal($keyword,
    				$stat3Day, $stat7Day, $tbStat3Day, $cateStat,
    				$wordsToUpdate, $wordsToDelete);
    	}
    	
    	if($keyword['max_price'] == -1) { // 删除
    		$wordsToDelete[] = $keywordId;
    		$autoLog['action'] = AutoMaintainLogActions::DEL_WORD;
    	} else if($keyword['max_price'] != $oriPrice) { // 改价
    		// debug
    		$this->logDebug('update keyword: ' . $keyword . ' - ' . $keyword['word'] . ' : ' . $oriPrice . ' => ' . $keyword['max_price']);
            $user_max_price = $this->_pqProduct  && $this->_pqProduct['default_price'] > 0 
            		? $this->_pqProduct ['default_price'] 
            		: (AutoDriveBaseModel::DEFAULT_WORD_MAX_PRICE < $this->_driveParams['maxPriceRatio'] * $tbStat3Day['avg_price'] 
            						? AutoDriveBaseModel::DEFAULT_WORD_MAX_PRICE
            						: $this->_driveParams['maxPriceRatio'] * $tbStat3Day['avg_price']);
    		$wordsToUpdate[$keywordId] = array(
    				'word' => $keyword['word'],
    				'keyword_id' => $keywordId,
    				'max_price' =>    $keyword['max_price'] > $user_max_price ? $user_max_price : $keyword['max_price'],
    				'matchscope' => $keyword['matchscope'] ? $keyword['matchscope'] : AutoDriveBaseModel::DEFAULT_MATCHSCOPE
    		);
    		$autoLog['action'] = $keyword['max_price'] > $oriPrice ? AutoMaintainLogActions::INC_PRICE : AutoMaintainLogActions::DEC_PRICE;
    	} else { // 不变
    		$autoLog['action'] = AutoMaintainLogActions::NO_CHANGE;
    	}
    	 
    	M('auto_maintain_log')->add($autoLog);
    }

    /**
     * @param array $keyword [引用传参] 一条keywords表记录
     * @param array(k=>v) $stat3Day 该关键词最近3天的表现.
     * @param array(k=>v) $stat7Day 该关键词最近7天的表现.
     * @param array(k=>v) $tbStat3Day 该关键词最近3天在淘宝全网的统计信息. TODO 或者使用关键词在该类目下的统计信息更准确.
     * @param array(k=>v) $cateStat 该类目的统计信息
     * 		包含 avg_price,click,pv,competition,ctr
     *
     * @param array2D $wordsToUpdate [引用传参] ( 'word' => '关键词' 'keyword_id' => 关键词ID, 'max_price' => 50 // 分为单位 'matchscope' => 1)
     * @param array(number) $wordsToDelete [引用传参] 要删除的关键词ID列表.
     * 	包含 cpc, ctr, impressions, cpm, click, cost, source, search_type, avgpos, keywordstr
     */
    protected function _maintainKeywordSave(&$keyword,
    		$stat3Day, $stat7Day, $tbStat3Day, $cateStat,
    		&$wordsToUpdate, &$wordsToDelete) {
    	$keywordId = $keyword['keyword_id'];
    	$oriPrice = $keyword['max_price'];

    	$lowAvgPosThres = 45;		// > 30为排名靠后
    	$topAvgPosThres = 16; 		// <=16为排名靠前
    	$lowQscoreThres = 3;		// <=2 为低
    	$middleQscoreThres  = 6;	// [3, 6]为中
    	$highQscoreThres = 8;		//>=8为高
    	$avePv = 5 * $tbStat3Day['pv'] / $tbStat3Day['competition']; // 平均pv
    	$highCtrPercent = 1;
    	$changePricePer = 0.1;
    	$changePrice=$tbStat3Day['avg_price'] * $changePricePer < 50 ? $tbStat3Day['avg_price'] * $changePricePer : 50; // TODO 加价怎么会加全体平均价呢
    	$createTime = strtotime($keyword['create_time']);
    	
    	$maxPrice = 0.8 * $cateStat['avg_price'];
    	
    	
    	
    }

    /**
     * 智能和高速模式下维护有pv的一个关键词。 关键指标 pv, ctr, cvr, qscore, avgpos
     *
     * @param array $keyword [引用传参] 一条keywords表记录
     * @param array(k=>v) $stat3Day 该关键词最近3天的表现.
     * @param array(k=>v) $stat7Day 该关键词最近7天的表现.
     * @param array(k=>v) $tbStat3Day 该关键词最近3天在淘宝全网的统计信息. TODO 或者使用关键词在该类目下的统计信息更准确.
     * @param array(k=>v) $cateStat 该类目的统计信息
     * 		包含 avg_price,click,pv,competition,ctr
     *
     * @param array2D $wordsToUpdate [引用传参] ( 'word' => '关键词' 'keyword_id' => 关键词ID, 'max_price' => 50 // 分为单位 'matchscope' => 1)
     * @param array(number) $wordsToDelete [引用传参] 要删除的关键词ID列表.
     * 	包含 cpc, ctr, impressions, cpm, click, cost, source, search_type, avgpos, keywordstr
     */
    protected function _maintainKeywordNormal(&$keyword,
    		$stat3Day, $stat7Day, $tbStat3Day, $cateStat) {
    	$keywordId = $keyword['keyword_id'];
    	$oriPrice = $keyword['max_price'];
    	
    	$lowAvgPosThres = 40;		// >40为排名靠后
    	$topAvgPosThres = 16; 		// <=16为排名靠前
    	$lowQscoreThres = 3;		// <=2 为低
    	$middleQscoreThres = 6;	// [3, 6]为中
    	$highQscoreThres = 8;		//>=8为高
    	$avePv = 5 * $tbStat3Day['pv'] / $tbStat3Day['competition']; // 平均pv
    	$highCtrPercent = 1;
    	$changePricePer = 0.1;
    	
    	$changePrice = $tbStat3Day['avg_price'] * $changePricePer < 50 ? $tbStat3Day['avg_price'] * $changePricePer : 50;
    	$createTime = strtotime($keyword['create_time']);

    	// 先删除：1)质量得分低的 2）长期没有pv且质量得分不高
    	if(($keyword['qscore'] <= $lowQscoreThres ) ||
    			($stat7Day['impressions'] == 0 && $keyword['qscore'] < $highQscoreThres && $createTime < time() - 7 * 86400) ) {
    		$keyword['max_price'] = -1;
    		return;
    	}
    	
    	// 检查转化率
    	$last3times = M('auto_maintain_log')
		    	->where(array('keyword_id'=>$keywordId, 'date'=>array('egt', date('Y-m-d', strtotime('-7 day')))))
		    	->select();
    	$cvrStatus = $this->_checkCvr($keyword, $last3times, $stat3Day, $tbStat3Day);
    	if($cvrStatus == -2) {
    		$keyword['max_price'] = -1;
    		return;
    	} else if($cvrStatus == -1) {
    		$keyword['max_price'] -= $changePrice;
    		return;
    	}
    	
    	if($stat3Day['impressions'] > $avePv) {								// 1. 高pv
    		if($stat3Day['ctr'] >= $highCtrPercent * $tbStat3Day['ctr'] ) {	// 2. 高点击率
    			if($tbStat3Day['pqr'] == 1) {								//3.高信价比
    				if($stat3Day['avgpos'] > $topAvgPosThres && $stat3Day['sales'] > 0) { // 4. 排名中后且有转化. TODO 如果是高速模式，可以锁定排名
    					$keyword['max_price'] += $changePrice;
    				}
    			} else if($tbStat3Day['pqr'] == -1) {						//3. 低信价比
    				if($stat3Day['avgpos'] < $lowAvgPosThres) { 			// 4. 排名中前. 
    					$keyword['max_price'] -= $changePrice;
    				}
    			}
    			
    		} else {									 					// 2. 低点击率
    			if($keyword['qscore'] > $middleQscoreThres) {				// 3. 中高质量得分
    				if($tbStat3Day['pqr'] == 1) {							// 4. 高信价比
    					//保留观察, 如果已经一周了还是无转化, 删除
    					if(count($last3times) >= 2) { // 至少需要2次记录
    						$i = 0;
    						foreach ($last3times as $lastTime) {
    							if($lastTime['pv'] == 2 && $lastTime['ctr'] < 2 
    									&& $lastTime['qscore'] > 1 && $lastTime['action'] == 1) {
    								++$i;
    							}
    						}
    						if($i >= 2){
    							$keyword['max_price'] = -1;
    						}
    					} else { //没有加价超过3次
	    					if($stat3Day['avgpos'] > $topAvgPosThres) { 	// 5. 排名靠后. TODO 最好可以锁定排名
	    						$keyword['max_price'] += $changePrice;
	    					}
    					}

    				} else if($tbStat3Day['pqr'] == -1) { 					// 4. 低信价比
    					if($stat3Day['avgpos'] < $lowAvgPosThres) { 		// 5. 排名已经很靠前. 降价。TODO 要根据大词还是小词来判断
    						$keyword['max_price'] -= $changePrice;
    					}
    				}
    			} else {													// 3. 中低质量得分
    				if($tbStat3Day['pqr'] == -1) { 							// 5. 低信价比
    					if($stat3Day['avgpos'] < $lowAvgPosThres) { 		// 5. 排名靠前. TODO 最好可以锁定排名
    						$keyword['max_price'] -= $changePrice;
    					}
    				}
    			}
    		}
    	} else if($stat3Day['impressions'] > 0) {	 						// 1. 低pv
    		// TODO 如果是长尾词，应该能允许高价
    		if( ($keyword['qscore'] > $middleQscoreThres) ||				// 2. 高质量得分, 或者 中质量得分但点击率高, 或者 有转化
    				($keyword['qscore'] <= $middleQscoreThres && $stat3Day['ctr'] >= $highCtrPercent * $tbStat3Day['ctr']) || 
    				$stat3Day['sales'] > 0 ) {
    			if($tbStat3Day['pqr'] == 1) {								// 3. 高信价比
    				if($stat3Day['avgpos'] > $topAvgPosThres) { 			// 4. 排名靠后. TODO 最好可以锁定排名
    					$keyword['max_price'] += $changePrice;
    				}
    			} else if($tbStat3Day['pqr'] == -1) {						// 3. 低信价比
    				if($stat3Day['avgpos'] < $lowAvgPosThres) { 			// 4. 排名靠前.
    					$keyword['max_price'] -= $changePrice;
    				}
    			}
    		} else if($stat7Day['avgpos'] <= 10 && $stat7Day['ctr']) { 		// 2. 低质量得分，排名靠前还没有点击
    			$keyword['max_price'] = -1;
    		}
    		
    	} else { 															// 1. pv=0无展现
    		if( ($keyword['qscore'] >= $highQscoreThres)) {					// 2. 高质量得分
    			if($tbStat3Day['pqr'] == 1) {								// 3. 高信价比
    				$keyword['max_price'] += $changePrice;
    			}
    		}
    	}
    }
    
    /**
     * 检查转化率。如果连续3天都有较多点击（平均点击>=10），但是没有转化，则删除。如果连续3天得平均点击>=4，而没有转化，则降价。
     * 
     * @return 0: 不变； 1：降价；2：删词
     */
    protected function _checkCvr($keyword, $last3times, $stat3Day, $tbStat3Day) {
    	$highAvgClick = 10;
    	$midAvgClick = 4;
//     	$stat7DayClick = $stat7Day['impressions'] * $stat7Day['ctr'];
    	
    	if(($stat3Day['click'] >= $highAvgClick || $stat3Day['click'] >= $midAvgClick) && $stat3Day['sales'] == 0) {
	    /*	$last3times = M('auto_maintain_log')
			    	->where(array('keyword_id'=>$keyword['keyword_id'], 'date'=>array('egt', date('Y-m-d', strtotime('-3 day')))))
			    	->select();
	    	if(count($last3times) >= 2) { // 至少需要2次记录
	    		$noSales = true;
	    		foreach ($last3times as $lastTime) {
	    			$noSales = $noSales && ($lastTime['cvr'] == 0);
	    		}
	    		// 
	    		if($noSales) {*/
	    			// 70个点击而没有转化
		    		if($stat3Day['click'] >= $highAvgClick) { 
		    			return -2;
		    		
	    			// 至少28个点击而没有转化
		    		} else { 
		    			return -1;
		    		}
	    	//	}
	    //	}
    	}
    	return 0;
    }
    
    /**
     * 根据一个空产品自动投放关键词. <strong>注意: </strong> 必须已经建立了推广组, 并且将adgroup_id更新至了auto_pro_product_queue表.
     *
     * @param array(string) $existentWords [可选]默认为null.
     * @param bool $isInit 是否是第一次初始化自动开车adgroup. 只有在这种情况下才更新auto_pro_product_queue记录的progress.
     * @param int $retAct 对结果的处理类型. 0: 直接提交到直通车. 1: 不提交到直通车, 而是返回选出来的词; 2: 返回json格式的所有别选词以及选出来的词.
     * @return true|array2D|json 如果$retAct为0, 返回true. 
     * 			 如果$retAct为1: array( array('word' => XX, 'max_price' => NN, 'matchscope' => NN) ).
     * 			如果$retAct为2 返回具有下面格式的json:
     * 			{
     * 				all_words : {word1 : weight1, word1 : weight2, ...}
     * 				chosen_words : { 
     * 						word1 : {word : word1,  max_price : NN, matchscope : NN},
     * 						word2 : {word : word2,  max_price : NN, matchscope : NN},
     * 						...
     * 					}
     *   		}
     * @throws
     */
    public function addProKeywords($existentWords = null, $isInit = false, $retAct = 0, $cus_default_price = 0) {
    	if(!$this->_adgroup)
    		throw_exception('还未调用init()初始化adgroup。');
    	 $this->_driveParams['retAct']=$retAct;
    	$productId = $this->_adgroup['num_iid'];
    	$campaignId = $this->_adgroup['campaign_id'];
    	
    	// 第一部分: 获取词
    	$usedWords = array();
    	$this->setCndWordsFromDb($usedWords);
    	$existentWords = $existentWords ? array_merge(array_keys($usedWords), $existentWords) : array_keys($usedWords);
    	$this->prepareWords($existentWords);
    	// 		dbg($this->_cndWords, '$this->_cndWords');
    	$this->logDebug('All words: ' . join(', ', array_keys($this->_cndWords)));
    	if($isInit)
    		$this->_getAutoProPqMdl()->updateProgress($productId, $campaignId, 40, ProductQueueStatus::UPDATING);
    
    	// 第二部分: 过滤词
    	$this->filterWords();
    	if($isInit)
    		$this->_getAutoProPqMdl()->updateProgress($productId, $campaignId, 60, ProductQueueStatus::UPDATING);

    	// 确定词之后, 获取其他数据
    	$this->_prepareOtherData();
    	 
    	// 第三部分: 排序
    	$this->sortWords();
    	$this->logDebug('After sort: ' . join(', ', array_keys($this->_cndWords)));
    	
    	// 去重
    	$this->rmDupWords();
    	
    	// 第四部分: 选词出价
    	$chosenWords = $this->selectWords($existentWords, 0, $cus_default_price);
    	$this->logDebug('final words to add: ' . json_encode($chosenWords));
    	if($isInit)
    		$this->_getAutoProPqMdl()->updateProgress($productId, $campaignId, 80, ProductQueueStatus::UPDATING);

    	// 第五部分: 提交到直通车或者返回
    	if($retAct == 0) { // 提交到直通车
    		$this->saveCndWordsToDb($chosenWords, $usedWords); // 将剩下来的词加入数据库
    		$this->submitWords($chosenWords);
    		return true;
    	} else if($retAct == 1) { // 一键填词返回
    		return $chosenWords;
    	} else { // if($retAct == 2) 测试专用返回
    		$res['chosen_words'] = $chosenWords;
    		$res['all_words'] = $this->_cndWords;
    		return json_encode($res);
    	}
    }
    
    /**
     * 提交关键词
     * @param array2D $chosenWords
     * @return 提交成功的词的个数
     */
    protected function submitWords($chosenWords) {
    	if(!$chosenWords) return;
    	
    	// 添加关键词
    	$adgroupId = $this->_adgroup['adgroup_id'];
    	$keywordMdl = $this->_getKeywordMdl();
    	$addedWords = $keywordMdl->addKeywords($this->_userId, $adgroupId, $chosenWords, $this->_getApiMdl(), true);
    	 
    	// 同步sqcore, 然后删除qscore<=3的词
    	sleep(1);
    	$qscoreUpdated = $keywordMdl->syncQscore($adgroupId);
    	$delWordsNum = !C('DEL_LOW_QSCORE_AFTER_ADD') ? 0 : $keywordMdl->deleteLowQSWords(
    			$this->_userId, $adgroupId, C('DEL_LOW_QSCORE_AFTER_ADD'), $this->_getApiMdl(), true);
    	
    	return count($addedWords) - $delWordsNum;
    }
    
    /** 
     * 选词出价
     * @param array(string) $existentWords
     * @param int[=0] $numToChoose 如果给定，则选出这么多个词；-1表示选择全部；留空(=0)则根据adgroup的容量来定数量。
     * @param int[=0] $cus_default_price 如果给定，则使用$cus_default_price来出价。
     * @return array2D
     */
    public function selectWords($existentWords, $numToChoose = 0, $cus_default_price = 0) {
    	$numToChoose = $numToChoose > 0 ? $numToChoose 
    			: ($numToChoose == -1 ? count($this->_cndWords) 
    					: AutoDriveBaseModel::MAX_WORDS_PER_ADGROUP - count($existentWords));
    	/*
    	$maxPrice = (!$this->_pqProduct || $this->_pqProduct['default_price'] == 0) ? AutoDriveBaseModel::DEFAULT_WORD_MAX_PRICE
    			: $this->_pqProduct['default_price'];
    			*/
    	$dp = $this->_driveParams;
    	$maxPrice = isset($dp['abs_max_price']) && $dp['abs_max_price'] ? $dp['abs_max_price'] : 
				( $this->_pqProduct && $this->_pqProduct['default_price'] > 0 
						? $this->_pqProduct ['default_price'] :  AutoDriveBaseModel::DEFAULT_WORD_MAX_PRICE);
    	
    	return $this->setPrice($maxPrice, $numToChoose, $cus_default_price);
    }
    /**
     * 对词进行排序. 重要的词在前面.
     * @return AutoDriveBaseModel 返回自身。
     */
    public function sortWords() {
    	$this->_checkData(array('Similarity', 'PQR'), false);
    	//arsort($this->_cndWords); // 按权重倒序排序
    	
    	// TODO 当前similarity因为标题相关性的最大值基本不可能达到1, 所以最好也用最大值进行归一化.
    	$sortSimWeight = $this->_driveParams['sortSimWeight'];
    	
    	// 计算排序得分, 并按排序得分倒序排序
    	$tmpWords = array();
    	foreach($this->_cndWords as $word => $weight) {
    		$tmpWords[$word] = $this->sortScore($word); //$this->_weight($word, $sortSimWeight); 
    	}
    	arsort($tmpWords);
    	
    	// 将排序后的词再copy回$this->_cndWords并保持最初的权重.
    	$res = array();
    	foreach($tmpWords as $word => $sortWeight) {
    		$res[$word] = $this->_cndWords[$word];
    	}
    	$this->_cndWords = $res;
    	
    	return $this;
    }
    
    /**
     * 利用词的长度和词在淘宝的全网统计属性去重。
     */
    public function rmDupWords() {
    	$this->_checkDataWordTbStats3Days();
    	$tbStats = $this->_driveData['WordTbStats3Days'];
    	$n = count($this->_cndWords);
    	// current word
    	$currWordOri = key($this->_cndWords);
    	$currWord = trim_all($currWordOri);
    	$currWordStat = $tbStats[$currWordOri];
    	$currWordLen = mb_strlen($currWord);
    	
    	$dupWrods = array();
    	
    	for($i = 1; $i < $n; ++$i) {
    		// next word
    		next($this->_cndWords);
    		$nextWordOri = key($this->_cndWords);
    		$nextWord = trim_all($nextWordOri);
    		$nextWordStat = $tbStats[$nextWordOri];
    		$nextWordLen = mb_strlen($nextWord);
    		
    		
    		// comapre
    		if($currWordLen == $nextWordLen && $currWordStat['pv'] == $nextWordStat['pv'] 
    				&& $currWordStat['click'] == $nextWordStat['click']) {
    			//unset($this->_cndWords[$nextWordOri]); // DEBUG
    			$dupWrods[] = $nextWordOri;
    			
    		} else {
    			$currWordOri = $nextWordOri;
    			$currWord = $nextWord;
    			$currWordStat = $nextWordStat;
    			$currWordLen = $nextWordLen;
    		}
    	}
    	reset($this->_cndWords);
    	
    	// 删除重复的词
    	foreach($dupWrods as $word) {
    		unset($this->_cndWords[$word]); 
    	}
    	
    	return $this;
    }
    
    /**
     * (相似度和性价比的)综合得分计算公式. 目前采取的线性加权.
     * 调用之前必须先保证$_driveData['Similarity']和$_driveData['PQR']已经有值了.
     * 
     * @param string $word	要计算综合得分的词
     * @param float $simWeight	相似度所占的权重. 范围为[0, 1].
     * @return float
     */
    protected function _weight($word, $simWeight) {
    	return $simWeight * $this->_driveData['Similarity'][$word] + (1 - $simWeight) * $this->_driveData['PQR'][$word];
    }
    
    /**
     * 过滤无用词
     * @return AutoDriveBaseModel 返回自身。
     */
    public function filterWords() {
    	if(!$this->_cndWords)
    		return $this;
    	
    	$this->filterShortWords();
    	
    	$this->filterBySex();
    	$this->filterBySeason();
    	$this->logDebug('After sex & season filter: ' . join(', ', array_keys($this->_cndWords)));
    	
    	$this->filterByTbStats();
    	$this->logDebug('After tb stats filter: ' . join(', ', array_keys($this->_cndWords)));
    	
    	// $this->filterByCateWordStat();
    	// $this->logDebug('After cate filter: ' . join(', ', array_keys($this->_cndWords)));
    	    	  
 		$this->filterBySimilarity();
    	$this->filterBlackListWords();
    	// if($this->_priceTypeId==4){//广散网模式过滤掉热门词
    	// 	$this->filterPopWords();
    	// }
    	// if($this->_priceTypeId==1){
    	// $this->filterByOtherAd();
    	// }
    	return $this;
    }

    /**
     * 获取一个推广组的所有候选关键词并设置到 $this->_cndWords中。
     * 格式为 array(word => weight). 具体权重见文档: Docs/AutoDrive/取词类型及权重.txt
     *
     * @param array(string) $excludedWords 需要排除的关键词(如果有英文，必须小写).
     * @return AutoDriveBaseModel 返回自身。
     */
    public function prepareWords($excludedWords = null) {
    	if(!$this->_adgroup)
    		throw_exception('还未调用init()初始化adgroup。');

    	if($this->_cndWords !== null) { // 如果已经通过setCndWordsFromDb()设置了除$rcmmWords之外的词。
    		$rcmmWords = $this->_getApiMdl()->getAdgroupRcmm($this->_userId, $this->_adgroup['adgroup_id']); // array of objects
    		foreach($rcmmWords as $rcmmWord)
    			$this->_cndWords[$rcmmWord->word] = 51;
    		
    		// TODO 可以获取高质量得分的相关词，或者其他方式获取更多词，这在$this->_cndWords用完的时候很重要。
    	
    	} else { // 否则需要设置全部
	    	$productMdl = $this->_getProductMdl();
	    	$productMdl->confirmProductTypeProps($this->_adgroup);
	    
	    	$allRelatedWords = array();
	    	
	    	// 直通车推荐关键词: weight = 51
	    	$rcmmWords = $this->_getApiMdl()->getAdgroupRcmm($this->_userId, $this->_adgroup['adgroup_id']); // array of objects
	    	$this->logDebug('rcmm words: ' . join(', ', array_column($rcmmWords, 'word', true, true)));
	    	foreach($rcmmWords as $rcmmWord) {
	    		$allRelatedWords[$rcmmWord->word] = 51;
	    	}
	        
	    	// 产品标题分词: weight = 61 ~ 66
	    	$titleWords = $productMdl->getTitleWords($this->_adgroup, 60);
	    	$this->logDebug('title words: ' . join(', ', array_keys($titleWords)));
	    	$allRelatedWords = array_merge($allRelatedWords, $titleWords);
	    
	    	// 用户自定义关键词: weight = 71
	    	$mykeywords = isset($this->_pqProduct['mykeywords']) ? explode(',', $this->_pqProduct['mykeywords']) : array();
	    	array_walk($mykeywords, create_function('&$item', '$item = zh_strtolower(trim($item));')); // trim掉每个词的前后空格
	    	$this->logDebug('user given words: ' . join(', ', $mykeywords));  // weight: 99
	    	foreach($mykeywords as $word) {
	    		$allRelatedWords[$word] = 71;
	    	}
	    
	    	// 属性关键词 : weight = 81 ~ 86
	    	$propWords = $productMdl->getPropTypeWords($this->_adgroup, 80);
	    	$this->logDebug('prop words: ' . join(', ', array_keys($propWords)));
	    	$allRelatedWords = array_merge($allRelatedWords, $propWords);
	    
	    	//  将类目热门词加入 52 
	    /*	$this->_checkData(array('CatTopWord'), false);	类目热词好多与宝贝本身不符，暂不加
    		$catTopwords=$this->_driveData['CatTopWord'];
    		$this->logDebug('cat top words: ' . join(', ', $catTopwords));
    		foreach($catTopwords as $word) {
	    		$allRelatedWords[$word] = 52;
	    	}*/
	    
	    	// 相关词: weight = 41
	    	/* $apiMdl = new ApiModel();
	    	$wordMdl = new WordModel();
	    	$relatedWords = $titleWords ? $this->_getWordMdl->getRelatedWords($titleWords, true , true) : array();
	    	$this->logDebug('related words: ' . join(', ', $relatedWords));
	    	foreach($relatedWords as $word) {
	    	$allRelatedWords[$word] = 41;
	    	} */
	    	
	    	$this->_cndWords = $allRelatedWords;
    	}
    
    	// 去掉需要排除的词
    	$this->logDebug('words to exclude: ' . ($excludedWords ? join(', ', $excludedWords) : 'null'));
    	if($excludedWords)
    		$this->_cndWords = array_diff_key($this->_cndWords, array_fill_keys($excludedWords, 0));
    
    	return $this;
    }

    /**
     * 根据开车策略和各项统计数据给关键词出价。
     *  TODO 也许还可以根据类目下的转化率来计算出价. 转化率越高的词, 出价越高.
     *
     * @param int $maxPrice 关键词的最高出价. 单位为分.
     * @param int $numLeft 要取的词的个数
     * @return 设定好价格的词。  array('word'=>XX, 'max_price'=>NN, 'matchscope'=>NN). 
     */
    protected function setPrice($maxPrice, $numLeft, $cus_default_price) {
    	if(!$this->_cndWords) return array();
    	
    	$this->_checkData(array('WordTbStats3Days', 'Similarity', 'PQR'), false); // , 'CateStat3Days'
		$tbStats = $this->_driveData['WordTbStats3Days'];
// 		$similarities = $this->_driveData['Similarity'];
		
		// 默认的取词阈值
		$maxPriceRatio = $this->_driveParams['maxPriceRatio'];
		$minPriceRatio = $this->_driveParams['minPriceRatio'];
		
    	$chosenWords = array();
    	$selectednum=0;
    	
    	// 超级省油模式出价
//     	$cateAvgPrice = $this->_driveData['CateStat3Days']['avg_price'];
//     	$superSaveWordPrice = $cateAvgPrice * 0.1 > 21 ? $cateAvgPrice * 0.1 : 21; // 最低出21分, 最高出类目平均价的10%.
    	
    	foreach($this->_cndWords as $word => $weight) {
    		$priceScore = $this->priceScore($word); // ($weight % 10 >= 5 || $weight % 10 == 3) ?  1 : $this->_weight($word, $priceSimWeight);
    		
    		if($priceScore > 0 || $selectednum < 100) {
//     			$price = 0;
//     			if($this->_priceTypeId == 4) { // 测试超级省油模式
//     				$price = $superSaveWordPrice;
//     			} else {
                    if((int)$cus_default_price > 0) {
                        $price = $cus_default_price;
                    } else {
                        $price = AutoDriveBaseModel::MIN_PRICE;
                        if(isset($tbStats[$word]['avg_price'])) {
                            $price = ($minPriceRatio + $priceScore * ($maxPriceRatio - $minPriceRatio)) * $tbStats[$word]['avg_price'];
                            $price = $price > $maxPrice ? $maxPrice :
                                    ($price >= AutoDriveBaseModel::MIN_PRICE ? $price : AutoDriveBaseModel::MIN_PRICE);
                        }
                    }
//     			}
    			
	    		$chosenWords[$word] = array(
	    				'word' => $word,
	    				'max_price' => $price,
	    				'matchscope' => AutoDriveBaseModel::DEFAULT_MATCHSCOPE,
    			);
	    		--$numLeft;
	    		++$selectednum;
    		}
    		if($numLeft <= 0)
    			break;
    	}
    	return $chosenWords;
    }
    
	/**
     * 根据出价策略和各项统计数据给出一系列关键词的出价。
     *
     * @param array() $words  需要推荐出价的关键词
     * @param int $maxPrice 关键词的最高出价. 单位为分.

     * @return 设定好价格的词。  array('word'=>XX, 'max_price'=>NN, 'matchscope'=>NN). 
     */
    public function setWordsPrice($words, $maxPrice) {
    	$avgPrice = array();
    	foreach($words as $k => $w){
    		$word = $w['word'] ? $w['word'] : $k;
    		$this->_cndWords[$word] = 99;
    		$avgPrice[$word] = $words[$k]['avg_price'];
    	}
    	
    	if(!$this->_cndWords) return array();
    	
    	$this->_checkData(array('Similarity', 'PQR'), false); // , 'CateStat3Days'

		// 默认的取词阈值
		$maxPriceRatio = $this->_driveParams['maxPriceRatio'];
		$minPriceRatio = $this->_driveParams['minPriceRatio'];
    	$chosenWords = array();
    	foreach($this->_cndWords as $word => $weight) {
    		$priceScore = $this->_weight($word, $this->_driveParams['priceSimWeight']);

    		if($priceScore > 0) {
    			$price = ($minPriceRatio + $priceScore * ($maxPriceRatio - $minPriceRatio)) * $avgPrice[$word];

    			$price = $price > $maxPrice ? $maxPrice :
    					($price >= AutoDriveBaseModel::MIN_PRICE ? $price : AutoDriveBaseModel::MIN_PRICE);
    		
	    		$chosenWords[$word] = array(
	    				'word' => $word,
	    				'max_price' => $price,
    			);
    		}
    	}
    	return $chosenWords;
    }
    
    /**
     * 计算给定词的价格得分
     * @param string $word
     * @return float
     */
    public function priceScore($word) {
    	if($this->sortScore($word) < $this->_driveParams['cutThres']) { 
    		return 0; 
    	} else {
	    	$weight = $this->_cndWords[$word];
	    	//$priceScore = ($weight % 10 >= 5 || $weight % 10 == 3) ?  1 : $this->_weight($word, $this->_driveParams['priceSimWeight']);
	    	$priceScore = $this->_weight($word, $this->_driveParams['priceSimWeight']);
	    	return $priceScore;
    	}
    }
    
    /**
     * 计算给定词的排序得分
     * @param string $word
     * @return float
     */
    public function sortScore($word) {
    	return $this->_weight($word, $this->_driveParams['sortSimWeight']);
    }

    /**
     * 计算类目相似度
     * @param array(k=>v) $cateStat 给定词的类目平均统计数据
     * @param array(k=>v) $tbStat 给定词的全网平均统计数据
     * @return float 类目相似度。区间为0～1.
     */
    protected function _cateSim($cateStat, $tbStat,$word) {
    	$catePv = isset($cateStat['pv']) ? $cateStat['pv'] + 1 : 1;
    	$tbPv = isset($tbStat['pv']) ? $tbStat['pv'] + 1 : 1;
    	if($tbPv==1){
    		$tbStat=$this->_getKeywordMdl()->getTbStats(array($word),
    				'pv',
    				date('Y-m-d', time() - 7 * 86400), date('Y-m-d', time() - 86400), true);
    		$tbPv=$tbStat[$word]['pv'];
    	}else if($catePv==1){
    		$catePv = $this->_driveData['WordCateStats7Days'][$word]['pv'];
    	}
    	
    	$s = ($catePv / $tbPv) * (1 -  (  1.0 / sqrt($tbPv) )  );
    
    	$s=$s>1.0 ? 1.0 : $s;
    	//$s=$s<$this->_driveParams['lowCateSimThres'] ? 0.001 : $s;
    	return $s;
    }
    
    /**
     * 清理关键词列表里全网数据表现很差的关键词.
     *
     * TODO 1) 设定更好的threshold. 2) 还可根据其他参数过滤
     *
     * @return AutoDriveBaseModel 返回自身。
     */
    protected function filterByTbStats() {
		$this->_checkDataWordTbStats3Days(false);
		$tbStats = $this->_driveData['WordTbStats3Days'];
		$this->logDebug('$tbStats at next line:');
		$this->logDebug(json_encode($tbStats));
    	foreach($this->_cndWords as $word => $weight) {
    		if(!isset($tbStats[$word]) || !isset($tbStats[$word]['pv']) || $tbStats[$word]['pv'] < 1) {
    			unset($this->_cndWords[$word]);
    		}
    	}
    	return $this;
    }
    
    /**
     * 利用关键词类目API来过滤不在该类目中出现过的关键词, 来保证关键词跟当前类目相关.
     *
     * 现在是利用最近3天的平均数据, 以免单日数据波动太大.
     *
     * @return AutoDriveBaseModel 返回自身。
     */
    protected function filterByCateWordStat() {
		$this->_checkData(array('WordTbStats3Days', 'WordCateStats3Days','WordCateStats7Days'), false);
		$tbStats = $this->_driveData['WordTbStats3Days'];
		$cateStats = $this->_driveData['WordCateStats3Days'];
		$lowCateSimThres = $this->_driveParams['lowCateSimThres'];
		
    	$this->logDebug('$cateWordStats: ' . json_encode($cateStats));
    	// 过滤词
    	foreach($this->_cndWords as $word => $weight) {
    		$cateStat = $cateStats[$word];
    		// 将pv太低的词过滤掉
    		if( ($cateStat['pv'] < 5 && $cateStat['click'] == 0)|| $this->_cateSim($cateStat, $tbStats[$word]) < $lowCateSimThres ) {
    			unset($this->_cndWords[$word]);
    		}
    	}
    	return $this;
    }

    /**
     * Similarity太低的词需要过滤掉。
     * @return AutoDriveBaseModel 返回自身。
     */
    protected function filterBySimilarity() {
    	$lowSimThres = $this->_driveParams['lowProdSimThres'];
    	$this->_checkDataProdSim(false);
    	foreach($this->_cndWords as $word => $weight) {
    		$sim = $this->_driveData['ProdSim'][$word];
    		// 将similarity太低的词过滤掉
    		if($sim < $lowSimThres) {
    			unset($this->_cndWords[$word]);
    		}
    	}
    	return $this;
    	 
    }
    
    /**
     * 清理关键词列表里跟产品标题性别相反的关键词.
     *
     * @return AutoDriveBaseModel 返回自身。
     */
    protected function filterBySex() {
    	$this->_getProductMdl()->confirmProductTypeProps($this->_adgroup);
    	$titleOppoSex = 1 - self::guessSex($this->_adgroup['title'], $this->_adgroup['product_props']);
    	if($titleOppoSex == 2) // 1 - (-1) = 2
    		return $this;
    
    	foreach($this->_cndWords as $word => $weight) {
    		if((self::guessSex($word))== $titleOppoSex) {
    			unset($this->_cndWords[$word]);
    		}
    	}
    	return $this;
    }
    
    /**
     * 清理关键词列表里跟产品标题季节不符的关键词.
     * 
     * TODO 如何避免有些品牌或者其他属性包含了四季中的某个字？？？
     *
     * @return AutoDriveBaseModel 返回自身。
     */
    protected function filterBySeason() {
    	$titleSeason = self::guessSeason($this->_adgroup['title'], $this->_adgroup['product_props']);
    	if($titleSeason) {
    		foreach($this->_cndWords as $word => $weight) {
    			$wordSeason = self::guessSeason($word);
    			if($wordSeason && (($wordSeason & $titleSeason) == 0)) { //如果词也有季节，且不包含标题的季节，删除之。
    				unset($this->_cndWords[$word]);
    			}
    		}
    	}
    	return $this;
    }
    
    /**
     * Filter words that are <= given bytes. A Chinese character is counted as 2 bytes, a English 
     * character is 1 byte.
     * 
     * @param int $bytes = 2
     * @return AutoDriveBaseModel
     */
    protected function filterShortWords($bytes = 2) {
    	foreach($this->_cndWords as $word => $weight)
    		if(zh_strlen($word)<= $bytes) 
    			unset($this->_cndWords[$word]);
    	return $this;
    }
    
 /**
     * Filter words that are too popular
     * character is 1 byte.
     * 
     * @param int $competitionR = 1
     * @return AutoDriveBaseModel
     */
    protected function filterPopWords($competition = 1) {
    	$this->_checkDataCatWordAvgPrice();
    	$this->_checkDataWordCateStats3Days();
    	$catStats=$this->_driveData['WordCateStats3Days'];
    	if(isset($this->_driveData['CatTopWordAvgCompe'])){
    		$wordAvgC=$this->_driveData['CatTopWordAvgCompe'];
    		foreach($this->_cndWords as $word => $weight)
    			if($catStats[$word]['competition']>= $wordAvgC*$competition) {
    			unset($this->_cndWords[$word]);
    			}
    	}
    	return $this;
    }
    
 /**
     * Filter words that are in the blacklist
     * @return AutoDriveBaseModel
     */
    protected function filterBlackListWords() {
    	$blacklist=array("新款");
    	foreach($blacklist as $word)
    		if(isset($this->_cndWords[$word])) {
    			unset($this->_cndWords[$word]);
    	}
    	return $this;
    }
    
  /**
     * 清理关键词列表里已经被非广撒网计划投放超过2次的关键词.
     *
     * @return AutoDriveBaseModel 返回自身。
     */
    protected function filterByOtherAd() {
    	//找到所有非广撒网计划
    	$adgroupMdl = new AdgroupModel();
    	$whr['user_id']=$this->_userId;
    	$adgroups=$adgroupMdl->field('adgroup_id,num_iid,campaign_id')->where($whr)->select( array( "assoc_key" => true ) );
    	//dump($adgroups);
    	$pqMdl=new ProductQueueModel();
    	$whr['type']=4;
    	$ext=$pqMdl->where($whr)->select();
    	foreach($ext as $ex){ 
    		if(isset($adgroups[$ex['adgroup_id']])) 		
    		unset($adgroups[$ex['adgroup_id']]);
    	}
    	$keywordMdl = new KeywordModel();
    	foreach($this->_cndWords as $word => $weight) {
    		//计算该词在其他非广撒网计划中出现的次数
    		if(count($adgroups)){
    			$sql='SELECT count(*) as c FROM keywords where adgroup_id IN ( ' . join(',', array_keys($adgroups)) . ') and word="'.$word.'"';
    			$num=$keywordMdl->query($sql);
    			if($num[0]['c']>=2) {
    				unset($this->_cndWords[$word]);
    			}
    		}
    	}
    	return $this;
    }
    /**
     * 自动设置自动推广组的标题. 如果当前推广计划全部为自动计划, 且还没有设置分时折扣, 则
     * 根据推广计划中的主要行业设置行业模板.
     */
    public static function autoSetSchedule($userId, $campaignId) {
    	$adgroupMdl = new AdgroupModel();
    	$manualAdNum = $adgroupMdl->where(array(
    			'user_id'=>$userId, 'campaign_id'=>$campaignId, 'drive_type'=>0
    	))->count();
    	if($manualAdNum == 0) { // 如果全为自动推广组
    		$campaignMdl = new CampaignModel();
    		$campSchedule = $campaignMdl->getCampaignSchedule($userId, $campaignId);
    		if(!$campSchedule) { // 且还未设置分时折扣
    			self::setSchedule($userId, $campaignId, 0, $campaignMdl);
    		}
    	}
    }
    
    /**
     * 设置自动开车的分时折扣设置
     *
     * @param int $userId
     * @param int campaign_id
     * @param string $template , 时间设置模板字符串，留空使用系统自带的模板
     * etc : 分时折扣的折扣字符串 : all --全时段无任何折扣设置
     * 		一个分号表示一周内的一天，
     * 		00:00-08:00:100, 表示00:00到08：00 是100%折扣
     * 							00:00-08:00:100,08:00-24:00:85;0;0;0;0;0;0
     * @return bool , true of false ， 设置成功或者失败
     */
    public static function setSchedule($userId, $campaignId , $cid = 0, $campaignMdl = null ){
    	ParamsCheckUtils::checkNumeric($campaignId);
    	//如果没有设置则是使用系统模板
    	if(!$campaignMdl) $campaignMdl = new CampaignModel();
    	//如果没有设定cid则通过campaign自动判断属于哪个商品目录id
    	if(!$cid) {
    		//$cids = M('adgroups')->where('campaign_id=' . $campaignId)->field('')
    		$cids = $campaignMdl->getCampaignCats($campaignId  , true);
    		$cid = array_pop( array_keys($cids) );
    	}
    
    	//如果不存在任何类目id，则返回false
    	if($cid) {
    		$schedule = $campaignMdl->getDefaultCampaignSchedule($cid);
    	} else {
    		return false;
    	}
    
    	if(!$schedule) {
    		//系统默认模板
    		$schedule = '00:00-02:00:76,02:00-08:00:60,08:00-10:00:97,10:00-17:30:100,17:30-19:30:95,19:30-23:00:115,23:00-24:00:90;00:00-02:00:76,02:00-08:00:60,08:00-10:00:97,10:00-17:30:100,17:30-19:30:95,19:30-23:00:115,23:00-24:00:90;00:00-02:00:76,02:00-08:00:60,08:00-10:00:97,10:00-17:30:100,17:30-19:30:95,19:30-23:00:115,23:00-24:00:90;00:00-02:00:76,02:00-08:00:60,08:00-10:00:97,10:00-17:30:100,17:30-19:30:95,19:30-23:00:115,23:00-24:00:90;00:00-02:00:76,02:00-08:00:60,08:00-10:00:97,10:00-17:30:100,17:30-19:30:95,19:30-23:00:115,23:00-24:00:90;00:00-02:00:76,02:00-08:00:60,08:00-10:00:97,10:00-17:30:100,17:30-19:30:95,19:30-23:00:115,23:00-24:00:90;00:00-02:00:76,02:00-08:00:60,08:00-10:00:97,10:00-17:30:100,17:30-19:30:95,19:30-23:00:115,23:00-24:00:90';
    	}
    
    	$ret = $campaignMdl->saveCampaignSchedule($userId, $campaignId, $schedule);
    	return !empty($ret);
    }
    
    /**
     * 搜索词库中与产品类型相关的关键词
     *
     */
    public function searchWordsRelated( $product_type , $weight , $pageSize = 200 , $page = 1){
    	$filter = array(
    			array( 'field' => 'avg_price' , "min" => 0 , "max" => 999 ),
    			array( 'field' => 'pv' , "min" => 0 , "max" => 10000000),
    			array( 'field' => 'click' , "min" => 0 , "max" => 100000),
    			array( 'field' => 'ctr' , "min" => 0 , "max" => 10000 ),
    			array( 'field' => 'competition' , "min" => 0 , "max" => 10000)
    	);
    
    	$service_params = array(
    			'mykeywords' => $product_type,
    			'cate_id' => $this->_cateId,
    			'is_not' => 1,
    			'page' => $page,
    			'pagesize' => $pageSize,
    			'filters' => json_encode($filter),
    			'order' => 'pv',
    			'asc' => 'false',
    			'timespan' => 'WEEK',
    	);
    	try{
    		$res = $this->_getCarine()->getFilterWords($service_params);
    		$result = json_decode($res, true);
    	}catch(Exception $e){
    		$this->logDebug('Auto Drive Error when $carine->keywordFilter() with params: ' . json_encode($service_params));
    		return array();
    	}
    	$recommend_words = $result["response"]["keywords"];
    	$words = array();
    	foreach($recommend_words as $k => $w){
    		$word = $w['word'] ? $w['word'] : $k;
    		$words[ $word ] = $weight;
    	}
    
    	return $words;
    }
    
    /**
     * 检查某个关键词里面包含的性别信息
     * @param string $word 要检查的关键词
     * @param string $props 产品的属性
     * @return int 0: 女; 1: 男; -1: 没有检测到.
     */
    public static function guessSex($word, $props = null) {
    	if($props) {
    		$propsArr = explode(',', $props);
    		foreach($propsArr as $prop) {
    			if($prop == '男') {
    				return 1;
    			} else if($prop == '女') {
    				return 0;
    			}
    		} 
    	}
    	$male = strpos($word, '男') !== false;
    	$female = strpos($word, '女') !== false;
    	return $male ? ($female ? -1 : 1) : ($female ? 0 : -1);
    }
    
    /**
     * 猜测给定词语中带有的季节信息。
     * @param string $word 要猜测的词或句子。
     * @return int 返回四季信息的位编码。如果不含四季，则返回0，否则返回 bbbb四位编码，依次表示冬秋夏春。
     * 		使用位操作，可以轻易比较2个词是否含有相同的季节信息。
     */
    public static function guessSeason($word, $props = null) {
    	$codes = array('春' => 1, '夏' => 2, '秋' => 4, '冬' => 8);
    	$code = 0;
    	// 先检查标题
    	preg_match_all('/\x{6625}|\x{590f}|\x{79cb}|\x{51ac}/u', $word, $matches);
    	if($matches) {
    		foreach ($matches[0] as $match) {
    			$code = $code | $codes[$match];
    		}
    	}
    	// 标题未检出，检查属性
    	if($props && !$code) {
    		preg_match_all('/\x{6625}|\x{590f}|\x{79cb}|\x{51ac}/u', $props, $matches);
    		if($matches) {
    			foreach ($matches[0] as $match) {
    				$code = $code | $codes[$match];
    			}
    		}
    	}
    	return $code;
    }
    
    /**
     * Getter of $_cndWords. 以便其他类使用本类的各种方法组合。
     * @return 返回属性$_cndWords
     */
    public function getCndWords() {
    	return $this->_cndWords;
    }
    
    /**
     * 将备选词存入数据库中。
     * 格式为：
     * WORD:WEIGHT:IS_CHOSEN,WORD:WEIGHT:IS_CHOSEN,...
     * 其中WEIGHT为权重，IS_CHOSEN为是否已经被选中（是=1，否=0）。
     * 
     * @param array2D $chosenWords 被选中的词。array( array(word=>XX, max_price=>NN, match_scope=>NN) ).
     * @return AutoDriveBaseModel
     */
    public function saveCndWordsToDb($chosenWords, $usedWords) {
    	$str = '';
//     	foreach($this->_cndWords as $word => $weight) {
//     		$cndWordsStr .= ',' . preg_replace('/[,:]/', '', $word) . ':' . $weight . ':' . (array_key_exists($word, $chosenWords)? 1 : 0);
//     	}
    	$nonUsedWords = array_diff_key($this->_cndWords, $chosenWords);
    	$chosenCndWords = array_intersect_key($this->_cndWords, $chosenWords);
    	$this->_getAutoProPqMdl()->where(array(
	    			'product_id' => $this->_pqProduct['product_id'],
	    			'campaign_id' => $this->_pqProduct['campaign_id'])
    			)->save(array(
    					'cand_words' => json_encode($nonUsedWords),
    					'used_words' => json_encode(array_merge($usedWords, $chosenCndWords)),
    			));
    	return $this;
    }
    
    /**
     * 从数据库中加载备选词
     * @param array(string)& $usedWords [引用传参]所有自动开车曾经用过的词（包括现在正在使用的词）. 返回的格式为：array(word => weight)
     * @return AutoDriveBaseModel
     */
    public function setCndWordsFromDb(&$usedWords) {
    	$cndNUsedWords = $this->_getAutoProPqMdl()->field('cand_words,used_words')->where(array(
	    			'product_id' => $this->_pqProduct['product_id'],
	    			'campaign_id' => $this->_pqProduct['campaign_id'])
    			)->find();
    	// 在下面设置$this->_cndWords=null表明是第一次自动开车，如果为空array()，表明以前自动开过，不过所有cand_words用完了。
    	$this->_cndWords = $cndNUsedWords['cand_words'] ? json_decode($cndNUsedWords['cand_words'], true) : null;
    	$usedWords = $cndNUsedWords['used_words'] ? json_decode($cndNUsedWords['cand_words'], true) : array(); // 先尝试用json_decode新版本: 保存了词及其权重
    	if(!$usedWords && $cndNUsedWords['used_words']) { // 上一步json_decode没有值，则表明为老版本: 只保存了词, 将其权重设为0.
    		$usedWords = explode(',', $cndNUsedWords['used_words']);
    		$usedWords = array_fill_keys($usedWords, 0);
    	}
    	return $this;
    }
    
    /**
     * 设置自动开车可调节参数
     * @param array(k=>v) $params
     */
    public function setDriveParams($params) {
    	foreach($params as $param => $val) {
    		$this->_driveParams[$param] = $val;
    	}
    	return $this;
    }
    
    /**
     * 获取开车所用数据.
     * @param mixed $dataName 具体的返回值要看 _checkData$dataName()方法中
     */
    public function getDriveData($dataName) {
    	return $this->_driveData[$dataName];
    }
    
    /**
     * 检查要获取的数据是否已经获得. 如果没有获取的话, 获取并存到$this->_driveData中.
     * 尽量只将需要在多个函数中都使用的变量放到这里. 只在一个函数中使用的需使用local变量.
     * 
     * @param array $paramNames 要获取的数据的名称. _checkData + $dataName 即为获取该参数的函数.
     * @param bool $forceRefresh = false 如果给定true, 不管如何都重新获取数据.
     * @return AutoDriveBaseModel 对象本身
     */
    protected function _checkData($dataNames, $forceRefresh = false) {
    	foreach($dataNames as $dataName) {
    		$funcName = "_checkData$dataName";
    		$this->$funcName($forceRefresh);
    	}
    	return $this;
    }
    
    /**
     * 检查关键词的综合相关度数据.
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel 对象本身
     */
    protected function _checkDataSimilarity($forceRefresh = false) {
    	if(!isset($this->_driveData['Similarity']) || $forceRefresh) {
	    	$this->_checkData(array('CateSim', 'ProdSim'), $forceRefresh);
	    	$sim = array();
	    	foreach($this->_cndWords as $word => $weight) {
	    		$powProb=pow($this->_driveData['ProdSim'][$word],1/2);
	    		$sim[$word] = pow($this->_driveData['CateSim'][$word] *$powProb,1/2);
	    	}
	    	$this->_driveData['Similarity'] = $sim;
    	}
    	return $this;
    }
    /**
     * 备选词的类目相关性.
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel
     */
    protected function _checkDataCateSim($forceRefresh = false) {
    	if(!isset($this->_driveData['CateSim']) || $forceRefresh) {
    		$this->_checkData(array('WordTbStats3Days', 'WordCateStats3Days','WordCateStats7Days'), $forceRefresh);
    		$tbStats = $this->_driveData['WordTbStats3Days'];
    		$cateStats = $this->_driveData['WordCateStats3Days'];
    		$sim = array();
	    	foreach($this->_cndWords as $word => $weight) {
// 	    		$catePv = isset($cateStats[$word]['pv']) ? $cateStats[$word]['pv'] + 1 : 1;
// 	    		$tbPv = isset($tbStats[$word]['pv']) ? $tbStats[$word]['pv'] + 1 : 1;
// 	    		$s = ($catePv / $tbPv) * (1 -  (  1.0 / sqrt($tbPv) )  );
// 	    		$sim[$word] = $s > 1.0 ? 1.0 : $s;
				if($word)
	    			$sim[$word] = $this->_cateSim($cateStats[$word], $tbStats[$word],$word);
	    	}
    		$this->_driveData['CateSim'] = $sim;
    	}    	
    	return $this;
    }
    
    /**
     * 备选词和产品标题(TODO 及属性, 但是目前没实现)的相似度.
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel
     */
    protected function _checkDataProdSim($forceRefresh = false) {
    	if(!isset($this->_driveData['ProdSim']) || $forceRefresh) {
    		$prodSim = $this->_getKeywordMdl()->getWordSim($this->_adgroup['title'], array_keys($this->_cndWords));
    		// 利用最大值再归一化一次
//     		$maxProdSim = max($this->_driveData['ProdSim']);
//     		array_walk($this->_driveData['ProdSim'], create_function('&$sim', '$sim = $sim/$maxProdSim'));

    		// 目前的服务给出的属性词与标题的相似度较低，需要特殊处理：把属性词当作完全匹配。
    		foreach($this->_cndWords as $word => $weight) {
    			if(in_array($weight, array(71, 83, 85, 86))) {
    				$prodSim[$word] = zh_strlen($word) / zh_strlen($this->_adgroup['title']);
    			}
    		}
    		$this->_driveData['ProdSim'] = $prodSim;
    	}
    	return $this;
    }
    /**
     * 根据3天淘宝全网数据, 计算备选词归一化过的性价比.
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel
     */
    protected function _checkDataPQR($forceRefresh = false) {
    	if(!isset($this->_driveData['PQR']) || $forceRefresh) {
    		$this->_checkDataWordTbStats3Days($forceRefresh);
    		$tbStats = $this->_driveData['WordTbStats3Days'];
    		$pqr = array();
    		$maxPqr = -1;
    		// 先求绝对性价比
    		foreach($this->_cndWords as $word => $weight) {
    			if(!isset($tbStats[$word]['avg_price']) || $tbStats[$word]['avg_price'] == 0) {
    				$pqr[$word] = -1;
    			} else {
	    			$tmpPqr = $tbStats[$word]['pv'] / ($tbStats[$word]['competition'] * $tbStats[$word]['avg_price']);
	    			$pqr[$word] = $tmpPqr;
	    			if($tmpPqr > $maxPqr){
	    				$maxPqr = $tmpPqr;
	    			}
    			}
    		}
    		// 再用最大值进行归一化
    		foreach($pqr as $word =>&$tmpPqr) {
    			$pqr0 = isset($tbStats[$word]['pv']) ? ($tbStats[$word]['pv']/10000 > 1 ? 1 : $tbStats[$word]['pv']/10000) : 0;
    			$tmpPqr = $pqr[$word] == -1 ? $pqr0 : ($maxPqr > 0 ? $tmpPqr / $maxPqr : 0);
    			$tmpPqr = pow($tmpPqr, 1/3);
    		}
    		$this->_driveData['PQR'] = $pqr;
    	}
    	return $this;
    }
    
    /**
     *  获取adgroup所在的行业的统计数据
     * @param string $forceRefresh
     */
    protected function _checkDataIndustryStats($forceRefresh = false) {
    	if(!isset($this->_driveData['IndustryStats']) || $forceRefresh) {
	    	$industryStat = $this->_getCategoryMdl()->getIndustryAvg(
	    			$this->_adgroup, array('trend_ppc_seller', 'trend_cvr_seller') );
	    	$industryStat['trend_ppc_seller'] *= 100; // 元转化为分
	    	$industryStat['trend_cvr_seller'] /= 100; // 百分号之前部分转化为小数
	    	$this->_driveData['IndustryStats'] = $industryStat;
    	}
    	return $this;
    }
    
/**
     *  获取adgroup所在的类目的top100词的3天的统计数据,得到被选关键词的均价
     * @param string $forceRefresh
     */
    protected function _checkDataCatWordAvgPrice($forceRefresh = false) {
    	if(!isset($this->_driveData['WordAvgPrice']) || $forceRefresh) { // 如果还没有获取候类目热词的统计数据，现在获取
    		$this->_checkData(array('CatTopWord'), $forceRefresh);
    		$words=$this->_driveData['CatTopWord'];
    		$num=0;
    		$sumcompetition=0;
    		$titleOppoSex = self::guessSex($this->_adgroup['title'], $this->_adgroup['product_props']);
    		foreach($words as $word=>$wordsStat){   		
    			$wordSex=self::guessSex($word);
    			if($titleOppoSex == -1||$wordSex == $titleOppoSex||$wordSex==-1) {
    				if($wordsStat['avg_price']>6){
    					$sumcompetition+=$wordsStat['competition'];
    					$num++;
    				}
    			}
    			
    		}

    		if($num>20){
    			$this->_driveData['CatTopWordAvgCompe']=$sumcompetition/$num;
    		}/*else{
    			$this->_checkDataIndustryStats();
    			$this->_driveData['CatTopWordAvgPrice']=$this->_driveData['IndustryStats']['trend_ppc_seller'];
    		}*/
    		
    		//get avg_price
    		$this->_checkData(array('WordTbStats3Days'), $forceRefresh);
    		$words=$this->_driveData['WordTbStats3Days'];
    		$num=0;$sum=0;
    		foreach($words as $word=>$wordsStat){   		
    			if($wordsStat['avg_price']>6){
    				$sum+=$wordsStat['avg_price'];
    				$num++;
    				if($num>200)
    					break;
    			}	
    		}
    		$this->_driveData['WordAvgPrice']=0;
    		if($num>0){
    			$this->_driveData['WordAvgPrice']=$sum/$num;
    		}
    	}
    	return $this;
    }
    /**
     *  获取adgroup所在的类目的top100词
     * @param string $forceRefresh
     */
 	protected function _checkDataCatTopWord($forceRefresh = false) {
    	if(!isset($this->_driveData['CatTopWord']) || $forceRefresh) { // 如果还没有获取候类目热词的统计数据，现在获取
    		$conditons['id'] = $this->_cateId;
    		$words = $this->_getCategoryMdl()->field('words')->where($conditons)->find();
    		$words = explode(',' , $words['words']);
    		if(!$words){
    			$words = $this->_getApiMdl()->getCateHotWords($this->_cateId, 100);
    		}  		
    		$this->_driveData['CatTopWord']=$words;
    	}
    	return $this;
    }
    
    /**
     * 获取关键词的3天表现数据(平均值)
     * @param bool $forceRefresh
     */
    protected function _checkDataWordStats3Days($forceRefresh = false) {
    	/* if($this->_driveData['WordStats3Days'] == null || $forceRefresh) {
	    	$this->_driveData['WordStats3Days'] = $this->_getRptKeywordMdl()->getSummaryStats(
	    			$userId, 'keyword_id', $keywordIds, 'impressions,ctr,click,pay_count,cpc,sales,avgpos,cost',
	    			date('Y-m-d', time() - $this->_driveParams['statDays'] * 86400), date("Y-m-d", time() -  86400), false, '', 'average');
    	}
    	return $this; */
    	return $this->_checkDataWordStatsXDays($this->_driveParams['statDays'], 
    			'impressions,ctr,click,pay_count,cpc,sales,avgpos,cost', 'summary', 'WordStats3Days');
    }
    
    /**
     * 获取关键词的给定天数的表现数据(平均值).
     * @param int $days
     * @param string $fields 逗号拼接的字符串. 示例: 'impressions,ctr,click,pay_count,cpc,sales,avgpos,cost'
     * @param string[=''] $name 默认为 'WordStats'.$days.'Days'.
     * @param string $forceRefresh
     * @param string $type 'average'|'summary'
     * @return AutoDriveBaseModel
     */
    protected function _checkDataWordStatsXDays($days, $fields, $type='summary', $name =  '', $forceRefresh = false) {
    	$name = $name ? $name : 'WordStats' . $days . 'Days';
    	if(!isset($this->_driveData[$name]) || $forceRefresh) {
    		if($this->_driveData['keywordIds'] == null) 
    			throw_exception("需要先设定_driveData['keywordIds']");
    		
    		$this->_driveData[$name] = $this->_getRptKeywordMdl()->getSummaryStats(
    				$this->_userId, 'keyword_id', $this->_driveData['keywordIds'], $fields,
    				date('Y-m-d', time() - $days * 86400), date("Y-m-d", time() -  86400), 
    				false, '', $type);
    	}
    	return $this;
    }
    
    /**
     * 获取备选词的3天淘宝全网数据(平均值).
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel
     */
    protected function _checkDataWordTbStats3Days($forceRefresh = false) {
    	if(!isset($this->_driveData['WordTbStats3Days']) || $forceRefresh) { // 如果还没有获取候选词统计数据，现在获取
    		$this->_driveData['WordTbStats3Days'] = $this->_getKeywordMdl()->getTbStats(array_keys($this->_cndWords),
    				'pv,avg_price,competition,click,ctr',
    				date('Y-m-d', time() - $this->_driveParams['statDays'] * 86400), date('Y-m-d', time() - 86400), true);
    	}
    	return $this;
    }
    /**
     * 获取备选词的3天类目下的数据(平均值).
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel
     */
    protected function _checkDataWordCateStats3Days($forceRefresh = false) {
    	if(!isset($this->_driveData['WordCateStats3Days']) || $forceRefresh) { // 如果还没有获取候选词统计数据，现在获取
//     		$this->logDebug('_checkDataWordCateStats3Days() words: ' . join(',', array_keys($this->_cndWords)));
//     		$this->logDebug('_checkDataWordCateStats3Days() cateId: ' . $this->_cateId);
    		$this->_driveData['WordCateStats3Days'] = $this->_getWordMdl()->getCatsWordData(
    				array_keys($this->_cndWords), $this->_cateId,
    				time() - $this->_driveParams['statDays'] * 86400, time() - 86400);
    	}
    	return $this;
    }
 /**
     * 获取备选词的7天类目下的数据(平均值).
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel
     */
    protected function _checkDataWordCateStats7Days($forceRefresh = false) {
    	if(!isset($this->_driveData['WordCateStats7Days']) || $forceRefresh) { // 如果还没有获取候选词统计数据，现在获取
//     		$this->logDebug('_checkDataWordCateStats3Days() words: ' . join(',', array_keys($this->_cndWords)));
//     		$this->logDebug('_checkDataWordCateStats3Days() cateId: ' . $this->_cateId);
    		$this->_driveData['WordCateStats7Days'] = $this->_getWordMdl()->getCatsWordData(
    				array_keys($this->_cndWords), $this->_cateId,
    				time() - 7 * 86400, time() - 86400);
    	}
    	return $this;
    }
    
    /**
	 * @deprecated 类目出价已取消. 现在得到的数据大多为0.
	 * 
     * 获取当前类目3天平均统计数据
     * @param bool $forceRefresh
     * @return AutoDriveBaseModel
     */
    protected function _checkDataCateStat3Days($forceRefresh = false) {
    	throw_exception('类目出价已取消. 该方法也被取消');
//     	if(!isset($this->_driveData['CateStat3Days']) || $forceRefresh) { // 如果还没有获取候选词统计数据，现在获取
//     		$this->_driveData['CateStat3Days'] = $this->_getCategoryMdl()->getCatBaseAvg($this->_cateId);
//     	}
    	return $this;
    }

    /**
     * @return RptKeywordModel
     */
    protected function _getRptKeywordMdl() {
    	if($this->_rptKeywordModel == null) {
    		$this->_rptKeywordModel = new RptKeywordModel();
    	}
    	return $this->_rptKeywordModel;
    }
    
    /**
     * @return Carine instance of carine
     */
    protected function _getCarine(){
    	import("@.Library.Carine");
    	if($this->_carine == null) {
    		$this->_carine = new Carine();
    	}
    	return $this->_carine;
    }

    /**
     * @return ProductModel
     */
    protected function _getProductMdl() {
    	if($this->_productMdl == null) {
    		$this->_productMdl = new ProductModel();
    	}
    	return $this->_productMdl;
    }

    /**
     * @return KeywordModel
     */
    protected function _getKeywordMdl() {
    	if($this->_keywordMdl == null) {
    		$this->_keywordMdl = new KeywordModel();
    	}
    	return $this->_keywordMdl;
    }

    /**
     * @return WordModel
     */
    protected function _getWordMdl() {
    	if($this->_wordMdl == null) {
    		$this->_wordMdl = new WordModel();
    	}
    	return $this->_wordMdl;
    }

    /**
     * @return ApiModel
     */
    protected function _getApiMdl() {
    	if($this->_apiMdl == null) {
    		$this->_apiMdl = new ApiModel();
    	}
    	return $this->_apiMdl;
    }

    /**
     * @return ProductQueueModel
     */
    protected function _getAutoProPqMdl() {
    	if($this->_autoProPqMdl == null) {
    		$this->_autoProPqMdl = new ProductQueueModel();
    	}
    	return $this->_autoProPqMdl;
    }

    /**
     * @return AdgroupModel
     */
    protected function _getAdgroupMdl() {
    	if($this->_adgroupMdl == null) {
    		$this->_adgroupMdl = new AdgroupModel();
    	}
    	return $this->_adgroupMdl;
    }

    /**
     * @return CategoryModel
     */
    protected function _getCategoryMdl() {
    	if($this->_cateMdl == null) {
    		$this->_cateMdl = new CategoryModel();
    	}
    	return $this->_cateMdl;
    }
    
}
